/**
 * 
 */
package name.webdizz.gadget.four.envelope.client.request.calback;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import name.webdizz.gadget.four.envelope.client.domain.Envelope;
import name.webdizz.gadget.four.envelope.client.domain.Expense;
import name.webdizz.gadget.four.envelope.client.domain.ExpenseItem;
import name.webdizz.gadget.four.envelope.client.domain.Goal;
import name.webdizz.gadget.four.envelope.client.domain.Person;
import name.webdizz.gadget.four.envelope.client.event.Events;
import name.webdizz.gadget.four.envelope.client.locale.Locale;
import name.webdizz.gadget.four.envelope.client.locale.LocaleFactory;
import name.webdizz.gadget.four.envelope.client.request.RequestCallback;
import name.webdizz.gadget.four.envelope.client.util.XmlEnvelopeParser;

import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;
import com.mvp4g.client.event.EventBus;

/**
 * The {@link RequestCallback} instance for analyzing envelope data.
 * 
 * @author Izzet_Mustafayev
 * 
 */
public class EnvelopeRequestCalback implements RequestCallback {

	private static final String ID = "id";
	/**
	 * The reference to {@link EventBus}.
	 */
	private EventBus eventBus;
	/**
	 * Holds received XML from remote application.
	 */
	private Document dom;

	/**
	 * The {@link Locale}.
	 */
	private static final Locale locale = LocaleFactory.instance();

	/**
	 * Create {@link RequestCallback} instance for analyzing envelope data.
	 * 
	 * @param eventBus
	 *            the {@link EventBus}
	 */
	public EnvelopeRequestCalback(final EventBus eventBus) {
		super();
		this.eventBus = eventBus;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seename.webdizz.gadget.four.envelope.client.request.RequestCallback#
	 * onResponseReceived(java.lang.String)
	 */
	public void onResponseReceived(final String response) {
		if (null != response) {
			dom = XMLParser.parse(response);
			if (null != dom) {
				Envelope envelope = Envelope.instance();
				envelope.setPersons(getEnvelopePersonData());
				envelope.setExpenses(getExpenses());
				envelope.setGoals(getGoals());
				envelope.setSize(getEnvelopeSize());
				// indicate we've successfully loaded envelope let's display it
				eventBus.dispatch(Events.ENVELOPE_WAS_LOADED, envelope);
			}
		} else {
			eventBus.dispatch(Events.ENVELOPE_WAS_NOT_LOADED, locale
					.errUnableLoadEnvelope());
		}
	}

	/**
	 * Retrieve {@link Envelope} size.
	 * 
	 * @return {@link Envelope} size
	 */
	private Float getEnvelopeSize() {
		Float result = 0.0F;
		NodeList nodeList = dom.getElementsByTagName("envelope");
		Node node = nodeList.item(0);
		if (null != node) {
			result = Float.valueOf(node.getAttributes().getNamedItem("size")
					.getNodeValue());
		}
		return result;
	}

	/**
	 * Get envelope data for each person.
	 * 
	 * @return {@link Person} list
	 */
	private List<Person> getEnvelopePersonData() {
		List<Person> personList = new ArrayList<Person>();
		NodeList personNodeList = dom.getElementsByTagName("person");
		int len = personNodeList.getLength();
		for (int i = 0; i < len; i++) {
			Node node = personNodeList.item(i);
			Person person = new Person(node.getAttributes()
					.getNamedItem("name").getNodeValue());
			String personId = node.getAttributes().getNamedItem(ID)
					.getNodeValue();
			person.setIdentity(personId);
			person.setExpenseList(gatherPersonExpenseList(personId, node
					.getChildNodes()));
			personList.add(person);
		}
		return personList;
	}

	/**
	 * Gather {@link Expense} for given person represented as XML
	 * {@link NodeList}.
	 * 
	 * @param personId
	 *            the {@link Person} identity
	 * @param nodeList
	 *            the person data
	 * 
	 * @return {@link Expense} list
	 */
	private List<Expense> gatherPersonExpenseList(final String personId,
			final NodeList nodeList) {
		List<Expense> expenses = new ArrayList<Expense>();
		if (null != nodeList) {
			int len = nodeList.getLength();
			for (int i = 0; i < len; i++) {
				Node node = nodeList.item(i);
				Expense expense = createExpense(node);
				expense.setPersonId(personId);
				expenses.add(expense);
			}
		}
		return expenses;
	}

	/**
	 * Create {@link Expense} from given XML {@link Node}.
	 * 
	 * @param node
	 *            the node to create {@link Expense} from
	 * @return created {@link Expense}
	 */
	private Expense createExpense(final Node node) {
		return XmlEnvelopeParser.createExpense(node);
	}

	/**
	 * Retrieves expense list from received XML data.
	 * 
	 * @return {@link Map} with week day date as a key and {@link ExpenseItem}
	 *         list for this day
	 */
	private Map<String, List<ExpenseItem>> getExpenses() {
		return getConcreteList("actualExpense");
	}

	/**
	 * Retrieves goals list from received XML data.
	 * 
	 * @return {@link Map} with week day date as a key and {@link ExpenseItem}
	 *         list for this day
	 */
	private Map<String, List<ExpenseItem>> getGoals() {
		return getConcreteList("actualGoalCredit");
	}

	/**
	 * The working-horse to create <strong>actualExpense</strong> and
	 * <strong>actualGoalCredit</strong> list.
	 * 
	 * @param nodeName
	 *            the current analyzing node name e.g.
	 *            <strong>actualExpense</strong> or
	 *            <strong>actualGoalCredit</strong>
	 * @return {@link Map} with week day date as a key and {@link ExpenseItem}
	 *         list for this day
	 */
	private Map<String, List<ExpenseItem>> getConcreteList(final String nodeName) {
		Map<String, List<ExpenseItem>> map = new HashMap<String, List<ExpenseItem>>();
		NodeList nodeList = dom.getElementsByTagName(nodeName);
		int len = nodeList.getLength();
		for (int i = 0; i < len; i++) {
			Node node = nodeList.item(i);
			Node plannedDate = node.getAttributes().getNamedItem("plannedDate");
			List<ExpenseItem> expenseItems = null;
			if (null != plannedDate
					&& !map.containsKey(plannedDate.getNodeValue())) {
				expenseItems = new ArrayList<ExpenseItem>();
				map.put(plannedDate.getNodeValue(), expenseItems);
			}
			if (null == expenseItems)
				expenseItems = map.get(plannedDate.getNodeValue());
			ExpenseItem item = null;
			if ("actualGoalCredit".equals(nodeName)) {
				item = new Goal();
				createExpenseItems(item, node.getChildNodes());
			}
			if ("actualExpense".equals(nodeName)) {
				item = new ExpenseItem();
				createExpenseItems(item, node.getChildNodes());
			}
			if (null != item) {
				item.setDate(plannedDate.getNodeValue());
				item.setDefaultAccount(node.getAttributes().getNamedItem(
						"defaultAccount").getNodeValue());
				expenseItems.add(item);
			}
		}
		return map;
	}

	/**
	 * Create {@link ExpenseItem} from given {@link NodeList}.
	 * 
	 * @param item
	 *            the {@link ExpenseItem}
	 * @param nodeList
	 *            the node list to analyze to create {@link ExpenseItem}
	 * 
	 * @return create {@link ExpenseItem} or null
	 */
	private ExpenseItem createExpenseItems(final ExpenseItem item,
			final NodeList nodeList) {
		ExpenseItem resItem = null;
		if (null != nodeList) {
			resItem = item;
			int len = nodeList.getLength();
			for (int i = 0; i < len; i++) {
				Node node = nodeList.item(i);
				if (null != node
						&& ("goal".equals(node.getNodeName()) || "expense"
								.equals(node.getNodeName()))) {
					resItem.setName(node.getAttributes().getNamedItem("name")
							.getNodeValue());
					resItem.setIdentity(Integer.valueOf(node.getAttributes()
							.getNamedItem(ID).getNodeValue()));
					resItem.setCurrency(XmlEnvelopeParser.createCurrency(node
							.getChildNodes()));
				}
				if (null != node && "planned".equals(node.getNodeName())) {
					resItem.setPlanned(Double.valueOf(node.getFirstChild()
							.getNodeValue()));
				}
				if (null != node && "actual".equals(node.getNodeName())) {
					String value = (null != node.getFirstChild() && !""
							.equals(node.getFirstChild().getNodeValue())) ? node
							.getFirstChild().getNodeValue()
							: "0";
					resItem.setActual(Double.valueOf(value));
				}
				if (null != node && "goalAccount".equals(node.getNodeName())) {
					((Goal) resItem).setAccount(node.getAttributes()
							.getNamedItem(ID).getNodeValue());
				}
			}
		}
		return resItem;
	}

}
